#pragma once

#include <unordered_map>

#include <G3D/Vector3.h>

#define MAP_FILENAME_EXTENSION2 ".vmtree"

#define FILENAMEBUFFER_SIZE 500

namespace vmap
{

    class StaticMapTree;
    class WorldModel;

    enum VMAPLoadResult
    {
        VMAP_LOAD_RESULT_ERROR,
        VMAP_LOAD_RESULT_OK,
        VMAP_LOAD_RESULT_IGNORED
    };

#define VMAP_INVALID_HEIGHT       -100000.0f            // for check
#define VMAP_INVALID_HEIGHT_VALUE -200000.0f            // real assigned value in unknown height case

    /**
     * @brief
     *
     */
    class ManagedModel
    {
    public:
        /**
         * @brief
         *
         */
        ManagedModel() : iModel(0), iRefCount(0) {}
        /**
         * @brief
         *
         * @param model
         */
        void setModel(WorldModel* model) { iModel = model; }
        /**
         * @brief
         *
         * @return WorldModel
         */
        WorldModel* getModel() { return iModel; }
        /**
         * @brief
         *
         */
        void incRefCount() { ++iRefCount; }
        /**
         * @brief
         *
         * @return int
         */
        int decRefCount() { return --iRefCount; }
    protected:
        WorldModel* iModel; /**< TODO */
        int iRefCount; /**< TODO */
    };

    /**
     * @brief
     *
     */
    typedef std::unordered_map<unsigned int, StaticMapTree*> InstanceTreeMap;
    /**
     * @brief
     *
     */
    typedef std::unordered_map<std::string, ManagedModel> ModelFileMap;

    enum DisableTypes
    {
        VMAP_DISABLE_AREAFLAG = 0x1,
        VMAP_DISABLE_HEIGHT = 0x2,
        VMAP_DISABLE_LOS = 0x4,
        VMAP_DISABLE_LIQUIDSTATUS = 0x8
    };

    /**
     * @brief
     *
     */
    class VMapManager
    {
    protected:
        // Tree to check collision
        ModelFileMap iLoadedModelFiles; /**< TODO */
        InstanceTreeMap iInstanceMapTrees; /**< TODO */

        /**
         * @brief
         *
         * @param pMapId
         * @param basePath
         * @param tileX
         * @param tileY
         * @return bool
         */
        bool _loadMap(unsigned int pMapId, const std::string& basePath, unsigned int tileX, unsigned int tileY);
        /* void _unloadMap(unsigned int pMapId, unsigned int x, unsigned int y); */

    public:
        // public for debug
        /**
         * @brief
         *
         * @param x
         * @param y
         * @param z
         * @return G3D::Vector3
         */
        G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const;
        /**
         * @brief
         *
         * @param pMapId
         * @return std::string
         */
        static std::string getMapFileName(unsigned int pMapId);

        /**
         * @brief
         *
         */
        VMapManager();
        /**
         * @brief
         *
         */
        ~VMapManager();

        /**
         * @brief
         *
         * @param pBasePath
         * @param pMapId
         * @param x
         * @param y
         * @return VMAPLoadResult
         */
        VMAPLoadResult loadMap(const char* pBasePath, unsigned int pMapId, int x, int y);

        /**
         * @brief
         *
         * @param pMapId
         * @param x
         * @param y
         */
        void unloadMap(unsigned int pMapId, int x, int y);
        /**
         * @brief
         *
         * @param pMapId
         */
        void unloadMap(unsigned int pMapId);

        /**
         * @brief
         *
         * @param pMapId
         * @param x1
         * @param y1
         * @param z1
         * @param x2
         * @param y2
         * @param z2
         * @return bool
         */
        bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2);
        /**
        fill the hit pos and return true, if an object was hit
        */
        /**
         * @brief
         *
         * @param pMapId
         * @param x1
         * @param y1
         * @param z1
         * @param x2
         * @param y2
         * @param z2
         * @param rx
         * @param ry
         * @param rz
         * @param pModifyDist
         * @return bool
         */
        bool getObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float pModifyDist);
        /**
         * @brief
         *
         * @param pMapId
         * @param x
         * @param y
         * @param z
         * @param maxSearchDist
         * @return float
         */
        float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist);

        /**
         * @brief
         *
         * @param pCommand
         * @return bool
         */
        bool processCommand(char* /*pCommand*/) { return false; }      // for debug and extensions

        /**
         * @brief
         *
         * @param pMapId
         * @param x
         * @param y
         * @param z
         * @param flags
         * @param adtId
         * @param rootId
         * @param groupId
         * @return bool
         */
        bool getAreaInfo(unsigned int pMapId, float x, float y, float& z, unsigned int& flags, int& adtId, int& rootId, int& groupId) const;
        /**
         * @brief
         *
         * @param pMapId
         * @param x
         * @param y
         * @param z
         * @param ReqLiquidType
         * @param level
         * @param floor
         * @param type
         * @return bool
         */
        bool GetLiquidLevel(unsigned int pMapId, float x, float y, float z, unsigned char ReqLiquidType, float& level, float& floor, unsigned int& type) const;

        /**
         * @brief
         *
         * @param basepath
         * @param filename
         * @return WorldModel
         */
        WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename, unsigned int flags = 0);
        /**
         * @brief
         *
         * @param filename
         */
        void releaseModelInstance(const std::string& filename);

        /**
         * @brief what's the use of this? o.O
         *
         * @param pMapId
         * @param x
         * @param y
         * @return std::string
         */
         // what's the use of this? o.O
        std::string getDirFileName(unsigned int pMapId, int /*x*/, int /*y*/) const
        {
            return getMapFileName(pMapId);
        }
        /**
         * @brief
         *
         * @param pBasePath
         * @param pMapId
         * @param x
         * @param y
         * @return bool
         */
        bool existsMap(const char* pBasePath, unsigned int pMapId, int x, int y);

        bool isMapLoadingEnabled() const { return(iEnableLineOfSightCalc || iEnableHeightCalc); }
        bool isLineOfSightCalcEnabled() const { return(iEnableLineOfSightCalc); }
        bool isHeightCalcEnabled() const { return(iEnableHeightCalc); }

        typedef bool(*IsVMAPDisabledForFn)(unsigned int entry, unsigned char flags);
        IsVMAPDisabledForFn IsVMAPDisabledForPtr;

#ifdef MMAP_GENERATOR
    public:
        void getInstanceMapTree(InstanceTreeMap& instanceMapTree);
#endif

        private:
            bool iEnableLineOfSightCalc; /**< TODO */
            bool iEnableHeightCalc; /**< TODO */
    };

}